//
//  Connection.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/01/17.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "Connections.h"
#import "NetSocket.h"
#import "Message.h"
#import "Peers.h"
#import <limits.h>

//fejta: TODO: low: move TransferConnection to its own class.
#import "Transfers.h"

#import "Shares.h"

#import "SolarSeekEngine.h"

NSString *MakeNSStringFromInternetAddress(unsigned int address) {
        unsigned char ip[4];
				address = NSSwapHostIntToBig(address);  // need to swap to big endian, if we want to access bytes in correct order
        memcpy(&ip, &address, sizeof(ip));
        return [NSString stringWithFormat:@"%i.%i.%i.%i", ip[0],ip[1],ip[2],ip[3]];
}

@interface Connection (Protected)
- (id)initWithSocket:(NetSocket *)sock;
- (void)setConnected:(BOOL)c;
@end

unsigned ConnectionCount=0;

@implementation Connection
- (id)init {
	return [self initWithSocket:nil];
}

- (id)initWithSocket:(NetSocket *)sock {
	if(self = [super init]) {
		if(sock==nil) {
			sock = [NetSocket netsocket];
		}
		else {
			_connected = [sock isConnected];
		}
		[sock setDelegate:self];
		_socket = [sock retain];
	}
//	NSLog(@"ALLOC: %d(%d) allocated. %d Connections",self,sock,++ConnectionCount);
	return self;
}

- (void)dealloc { 
//	NSLog(@"DEALLOC: %d(%d) deallocated. %d Connections",self,_socket,--ConnectionCount);
	if([_socket delegate] == self) {
		[_socket close];
		[_socket setDelegate:nil];
	}
	[_socket release];
	_socket = nil;
	[NSObject cancelPreviousPerformRequestsWithTarget:self];
	[super dealloc];
}


#pragma mark actions

- (void)connectToHost:(NSString *)h onPort:(UInt16)p {
	NSAssert3(h && [h isEqual:@"0.0.0.0"]==NO,@"Cannot connect to %@:%d.  Invalid host %@",h,p,h);
	NSAssert3(p != 0,@"Cannot connect to %@:%d.  Invalid port %d",h,p,p);
	[_socket close];
	if([_socket open]) {
		if([_socket scheduleOnCurrentRunLoop]) {
			if([_socket connectToHost:h port:p] == NO) {
				[NSException raise:@"ConnectionException" format:@"CONNECTION (error): failed sending connectToHost:%@ port:%d message.",h,p];
			}
		}
		else {
			[NSException raise:@"ConnectionException" format:@"CONNECTION (error): failed scheduling socket on run loop."];
		}
	}
	else [NSException raise:@"ConnetionException" format:@"CONNECTION (error): failed opening socket."];
}

- (void)disconnect {
	[_socket close];
	if(_connected) {
		[self setConnected:NO];
	}
}

#pragma mark KVO/C methods.

- (NSString *)addressHost { return [_socket remoteHost];}
- (NSString *)addressLocal { return [_socket localHost]; }
- (BOOL)connected { return _connected; }
- (CFStreamError)errorDisconnection { return errorDisconnectionLast; }
- (UInt16) portHost { return [_socket remotePort]; }
- (UInt16) portLocal { return [_socket localPort]; }

- (void)setConnected:(BOOL)c { 
//	NSLog(@"CONNECTION: Setting %@:%d's connection to %d (reality is %d).",[self addressHost],[self portHost],c,[_socket isConnected]);
	if(c!= [_socket isConnected]) {
		NSLog(@"ERROR: %@:%d is actually %d but we are setting connected to %d.",[self addressHost],[self portHost],[_socket isConnected],c);
	}
	_connected = c; 
}

#pragma mark Socket delegate functions

- (void)netsocketConnected:(NetSocket *)sock {
	//netsockets always call this function upon connetion AND disconnection!
//	if([_socket isConnected]) NSLog(@"CONNECTION: %@:%d %@.",[sock remoteHost],[sock remotePort],[_socket isConnected]?@"connected":@"failed connection.");
	NSAssert(sock==_socket,@"NetSockets do not match (onSocket:didConnectToHost:port:).");
	[self willChangeValueForKey:@"portHost"];
	[self willChangeValueForKey:@"portLocal"];
	[self willChangeValueForKey:@"addressHost"];
	[self willChangeValueForKey:@"addressLocal"];
	[self didChangeValueForKey:@"addressLocal"];
	[self didChangeValueForKey:@"addressHost"];
	[self didChangeValueForKey:@"portLocal"];
	[self didChangeValueForKey:@"portHost"];
	[self setConnected:[_socket isConnected]];
}

- (void)netsocketConnectionTimedOut:(NetSocket *)sock {
	//this happens when we try to connect but we reach a timer before hand.

	NSAssert(sock==_socket,@"NetSockets do not match (onSocket:didConnectToHost:port:).");

	[self disconnect];
}

- (void)netsocketDisconnected:(NetSocket *)sock {
	NSAssert(sock==_socket,@"Sockets do not match (onSocketDidDisconnect:).");
	[self disconnect];
}


@end

#pragma mark -

@interface NetworkConnection (Private) 
- (void)transmitMessages;
- (void)setMessageSending:(Message *)message;
- (void)setMessageSent:(Message *)message;
- (void)setMessageReceived:(Message *)message;
- (void)updateStatus;
@end

@implementation NetworkConnection
- (id)init {
	return [self initWithSocket:nil];
}


- (id)initWithSocket:(NetSocket *)sock {
	if(self = [super initWithSocket:sock]) {
		_messagesQueued = [[NSMutableArray alloc] initWithCapacity:5];
		_messageReceived = nil;
		_messageSending = nil;
		_messageSent=nil;
		_messagesTransmitting = [[NSMutableArray alloc] initWithCapacity:3];
		automaticallyReadMessages = YES;
		_lengthMessage = 0;
		_stateConnection = MessageNone;
	}
	return self;
}

- (void)dealloc {
	_stateConnection = MessageNone;
	[_messagesQueued release];
	[_messageReceived release];
	[_messageSending release];
	[_messageSent release];
	[_messagesTransmitting release];
	[super dealloc];
}

#pragma mark actions

- (void)disconnect {
	_stateConnection = MessageNone;
	[super disconnect];
}

- (void)readNextMessage {
//	NSLog(@"MESSAGE: Waiting for next message from %@:%d.",[self addressHost],[self portHost]);
	_stateConnection = MessageLength;
	[self performSelector:@selector(updateStatus) withObject:nil afterDelay:0.0];
}

- (void)removeMessagesUnsent {
	[_messagesQueued removeAllObjects];
	[self setMessageSending:nil];
}

- (void)sad:(Message *)m {
//	if([_messagesQueued containsObject:m]) {
//		NSLog(@"We are removing a message from %@:%d.",[self addressHost],[self portHost]);
//	}
	[_messagesQueued removeObject:m];
}

- (void)sendMessage:(Message *)m {
	[_messagesQueued addObject:m];
	[self performSelector:@selector(sad:) withObject:m afterDelay:60.0];
//	[_messagesQueued performSelector:@selector(removeObject:) withObject:m afterDelay:60.0];
	if([self connected]) {
		[self performSelector:@selector(transmitMessages) withObject:nil afterDelay:0.0];
	}
	else {
		if(_messageSending==nil) {
			[self setMessageSending:m];
			[self performSelector:@selector(setMessageSending:) withObject:nil afterDelay:60.0];
		}
	}
}

- (void)sendMessagesQueued {
	if([self connected]) {
		[self performSelector:@selector(transmitMessages) withObject:nil afterDelay:0.0];
	}
	else
		NSLog(@"ERROR: didn't send queued messages to %@:%d!",[self addressHost],[self portHost]);
}

- (void)transmitMessages {
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(transmitMessages) object:nil];
	NSMutableData *buffer = [NSMutableData dataWithCapacity:100];
	NSEnumerator *e = [_messagesQueued objectEnumerator];
	Message *m;
	while(m = [e nextObject]) {
		NSData *d = [m data];
		unsigned int len = [d length];
		len = NSSwapHostIntToLittle(len);

		NSMutableData *mdata = [NSMutableData dataWithCapacity:[d length]+sizeof(len)];
		[mdata appendBytes:&len length:sizeof(len)];
		[mdata appendData:d];
		
		[_messagesTransmitting addObject:m];
		[buffer appendData:mdata];
	}
//	NSLog(@"MESSAGE: Sending %d messages (%d bytes) to %@:%d (%d).",[_messagesQueued count],[buffer length],[self addressHost],[self portHost],self);
	[_socket writeData:buffer];
	[_messagesQueued removeAllObjects];
}

- (void)printUnreadBytesInMessage:(Message *)message {
	NSData *data = [message data];
	unsigned len = [data length];
	NSRange r = NSMakeRange(len - [message countBytesUnread],[message countBytesUnread]);
	unsigned char *chars = (unsigned char *)malloc(r.length);
	[data getBytes:chars range:r];
	int i=0;
	unsigned char *c=chars;
	unichar uni[2];
	NSMutableString *ms = [NSMutableString stringWithString:@""];
	while(i++ < [message countBytesUnread]) {
		int num = *c;
		num = num >> 4;
		if(num >= 10) {
			uni[0] = num-10+'A';
		}
		else
			uni[0] = num + '0';
		num = *c;
		num %= 16;
		if(num >= 10) {
			uni[1] = num-10+'A';
		}
		else
			uni[1] = num + '0';
		NSString *s = [NSString stringWithCharacters:uni length:2];
		[ms appendString:s];
		if(i%16 == 0) {
			NSLog(ms);
			[ms setString:@""];
		}
		else if(i%8 == 0) [ms appendString:@"  "];
		else 		[ms appendString:@" "];
		c++;

	}
	NSLog(ms);
	free(chars);
}
	

- (void)updateStatus { 
	unsigned amount = [_socket incomingBufferLength];
	switch(_stateConnection) {
		case MessageLength: {
			if(amount>=4) {
				NSData *dataLength = [_socket readData:4];
				unsigned int length = NSNotFound;
				[dataLength getBytes:&length];
				length = NSSwapLittleIntToHost(length);
				if(length == 0) {
					NSLog(@"CONNECTION: %@:%d sent a zero length message.  Closing socket.",[self addressHost],[self portHost]);
					[self disconnect];
				}
				else if(length > (5*1024*1024)) {
					NSLog(@"CONNECTION: %@:%d sent an excessively long message (%d bytes).  Closing connection.",[self addressHost],[self portHost]);
					[self disconnect];
				}
				else {
					_lengthMessage = length;
					_stateConnection = MessagePayload;
				}
			}
			else
				return;
			break;
		}
		case MessagePayload: {
			if(amount >= _lengthMessage) {
				_stateConnection = MessageNone;
				NSDate *d = [[NSDate alloc] init];
				Message *m = nil;
				NS_DURING
					NSData *data = [_socket readData:_lengthMessage];
					m = [Message messageWithData:data];
//					[m rewind];
//					NSLog(@"MESSAGE: %@:%d received a message with code %d.",[self addressHost],[self portHost],[m getCode]);
					[m rewind];
					[self setMessageReceived:m];
//					if([m countBytesUnread]) {
//						[m rewind];
//						NSLog(@"MESSAGE: %@:%d received a %d code message but failed to read %d bytes (%d total).",[self addressHost],[self portHost],[m getCode],[m countBytesUnread],[[m data] length]);
//						[self printUnreadBytesInMessage:m];
//					}
				NS_HANDLER
					NSLog(@"Encountered an exception while receiving a message and will disconnect socket: %@",localException);
					[self disconnect];
					[d release];
					NS_VOIDRETURN;
				NS_ENDHANDLER
				NSDate *d2 = [[NSDate alloc] init];
				if([d2 timeIntervalSinceDate:d]>1.0) {
					[m rewind];
					[NSApp appendLogWithHeader:@"MESSAGE"
					  HTML:[NSString stringWithFormat:@"Took %.4f seconds to process message with code %d.",[d2 timeIntervalSinceDate:d],[m getCode]]
					  type:LogTypeLoud];
				}
				[d release];
				[d2 release];
//				NSLog(@"MESSAGE: Finished reading a %d byte message from %@:%d.", _lengthMessage,[self addressHost],[self portHost]);
				if(automaticallyReadMessages) 
					[self readNextMessage];
			}
			else {
//				NSLog(@"MESSAGE: %d/%d bytes available from %@:%d.", amount, _lengthMessage, [self addressHost],[self portHost]);
				return;
			}
			break;
		}
		default:
			return;
	}
	
	if([self connected] && [_socket incomingBufferLength] > 0) {
//		NSLog(@"updateStatus: sending for %d",self);
		[self performSelector:@selector(updateStatus) withObject:nil afterDelay:0.0];
	}
}


#pragma mark KVO/C methods.

- (BOOL)hasMessagesToSend { return _messageSending != nil; }

- (Message *)messageReceived { return _messageReceived; }
- (Message *)messageSending { return _messageSending; }
- (Message *)messageSent { return _messageSent; }

- (void)setMessageReceived:(Message *)message {
	[_messageReceived release];
	_messageReceived = [message retain];
}

- (void)setMessageSent:(Message *)message {
	[_messageSent release];
	_messageSent = [message retain];
}

- (void)setMessageSending:(Message *)message {
//	NSLog(@"MESSAGE: Setting the sending message.");
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(setMessageSending:) object:nil];
	[_messageSending release];
	_messageSending = [message retain];
}
	
#pragma mark Socket delegate methods

- (void)netsocketConnected:(NetSocket *)sock {
	NS_DURING
	[super netsocketConnected:sock];
	if([_socket isConnected]) {
		[self sendMessagesQueued];
		if(automaticallyReadMessages) {
			[self readNextMessage];
		}
	}
	NS_HANDLER
		NSLog(@"Encountered an error during connection: %@.",localException);
		[self disconnect];
	NS_ENDHANDLER
}

- (void)netsocketDataAvailable:(NetSocket *)sock {
	if(sock != _socket) {
		NSLog(@"Mismatched sockets during netsocketDataAvailable");
		[self disconnect];
		return;
	}
	
	[self updateStatus];
}

- (void)netsocketDataSent:(NetSocket *)sock {
	if(sock != _socket) {
		NSLog(@"Mismatched sockets during netsocketDataSent");
		[self disconnect];
		return;
	}
	NSEnumerator *e = [_messagesTransmitting objectEnumerator];
	Message *m;
	while(m = [e nextObject]) {
		[self setMessageSent:m];
		if(_messageSending && _messageSent == _messageSending) {
//			NSLog(@"MESSAGE: Sent the sending message for %@:%d.",[self addressHost],[self portHost]);
			[self setMessageSending:nil];
		}	
	}
	[_messagesTransmitting removeAllObjects];
}


@end

#pragma mark -

@interface PeerConnection (Protected) 
- (id)initWithPeer:(Peer *)peer socket:(NetSocket *)sock;

- (NSString *)connectionType;
- (unsigned)token;
@end


@implementation PeerConnection 

- (id)init {
	[self release];
	return nil;
}

- (id)initWithUnidentifiedConnection:(UnidentifiedConnection *)uc peer:(Peer *)peer {
	return [self initWithPeer:peer socket:[uc socket]];
}

- (id)initWithPeer:(Peer *)peer { return [self initWithPeer:peer socket:nil];}

- (id)initWithPeer:(Peer *)peer socket:(NetSocket *)sock{
	if(self = [super initWithSocket:sock]) {
//		NSLog(@"INIT: init with %@.",peer);
		_peer = [peer retain];
		_firstMessage = nil;
	}
	return self;
}

- (void)dealloc {
//	NSLog(@"DEALLOC: deallocating %@.",_peer);
	[_peer release];
	_peer = nil;
	[_firstMessage release];
	[super dealloc];
}

- (unsigned)hash {
	return [_peer hash];
}

- (BOOL)isEqual:(id)other {
	return self==other || [other isKindOfClass:[PeerConnection class]] && [_peer isEqual:[other peer]];
}

- (NSComparisonResult)compare:(PeerConnection *)other {
	return [other isKindOfClass:[self class]]?[_peer compare:[other peer]]:NSOrderedAscending;
}

#pragma mark KVO/C

- (unsigned)token { return [_peer token]; }
- (NSString *)connectionType { return TypeConnectionPeer; }
- (Peer *)peer { return _peer; }
- (BOOL)hasMessagesToSend { return _firstMessage != nil || [super hasMessagesToSend]; }
- (BOOL)sentHeader { return _firstMessage == nil; }
- (BOOL)piercingFirewall { return _piercingFirewall; }
- (unsigned)tokenFirewall { 
	NSAssert([self sentHeader]==NO,@"Firewall token already sent and consumed.");
	[_firstMessage rewind];
	char b = [_firstMessage getByte];
	NSAssert1(b==PierceFireWall,@"First message is not a PierceFirewall message.  Code is %d.",b);
	return [_firstMessage getInt];
}   

#pragma mark actions

- (void)changeSocket:(NetSocket *)socket {
//NSLog(@"CONNECTION: Changing %@:%d to %@:%d.",[_socket remoteHost],[_socket remotePort],[socket remoteHost				],[_socket remotePort]);
	if([_socket delegate] == self && socket != _socket) {
		[_socket close];
		[_socket setDelegate:nil];
	}
	[socket retain];
	[_socket release];
	_socket = socket;
	[_socket setDelegate:self];
	[_socket scheduleOnCurrentRunLoop];  //shouldn't have to but we are having issues!
	[_messagesQueued removeObject:_firstMessage];
	[_firstMessage release];
	_firstMessage = nil;
	[self netsocketConnected:socket];
}

- (void)connectToHost:(NSString *)hostname onPort:(UInt16)port {
	[NSException raise:@"InvalidMethodException" format:@"Please include username when connecting."];
}


- (void)connectToHost:(NSString *)hostname onPort:(unsigned)port withUsername:(NSString *)myUsername {
	_piercingFirewall=NO;
	Message *m = [Message message];
	[m putByte:PeerInit];
	[m putString:myUsername];
	[m putString:[self connectionType]];
	[m putInt:100];	//unknown (most likely unused) token.
	[_messagesQueued removeObject:_firstMessage];
	[_firstMessage release];
	_firstMessage = [m retain];
	[super connectToHost:hostname onPort:port];
}

- (void)pierceFirewallWithToken:(int)token ForHost:(NSString *)hostname onPort:(UInt16)port {
	_piercingFirewall=YES;
	Message *m = [Message message];
	[m putByte:PierceFireWall];
	[m putInt:token];
	[_messagesQueued removeObject:_firstMessage];
	[_firstMessage release];
	_firstMessage = [m retain];
	[super connectToHost:hostname onPort:port];
}

- (void)removeMessagesUnsent {
	[_firstMessage release];
	_firstMessage = nil;
	[super removeMessagesUnsent];
}

- (void)sendMessagesQueued {
	if(_firstMessage) {
		unsigned count = [_messagesQueued count];
		[_messagesQueued insertObject:_firstMessage atIndex:0];
	}
	[super sendMessagesQueued];
}

- (void)setMessageSent:(Message *)message {
	if(message == _firstMessage) {
//		NSLog(@"MESSAGE: Sent the first message.");
		[_firstMessage release];
		_firstMessage = nil;
	}
	[super setMessageSent:message];
}

@end

#pragma mark -


@implementation ServerConnection
- (id)init {
	if(self = [super init]) {
		srand([[NSDate date] timeIntervalSince1970]);
		_tokenLast = (rand()%32768)+2933;
	}
	return self;
}

- (unsigned)tokenNext {
	_tokenLast+=(rand()%50);
	if(_tokenLast >= __INT_MAX__)
		_tokenLast %= __INT_MAX__;
	return _tokenLast;
}

- (void)connectToHost:(NSString *)hostname onPort:(UInt16)port {
	[self removeMessagesUnsent];
	[super connectToHost:hostname onPort:port];
}

		
@end

#pragma mark -

@implementation UnidentifiedConnection 
- (id)init {
	[NSException raise:@"InvalidInitiationException" format:@"Please use initWithSocket: with a non-null socket to initialize this class."];
	[self release];
	return nil;
}

- (id)initWithSocket:(NetSocket *)sock {
	NSAssert(sock!=nil,@"UnidentifiedConnection initWithSocket: sock must not be nil.");
	if(self=[super initWithSocket:sock]) {
		automaticallyReadMessages = NO;
		info = nil;
		type = nil;
	}
	return self;
}

- (void)dealloc {
	[info release];
	[type release];
	[super dealloc];
}

#pragma mark KVO

- (NSString *)name { 
	NSAssert(info != nil,@"Name has not been set.");
	NSAssert([info isKindOfClass:[NSString class]],@"This connection was not set up with a name.");
	return (NSString *)info; 
}

- (NetSocket *)socket { return _socket; }

- (unsigned)token {
	NSAssert(info != nil,@"Token has not been set.");
	NSAssert([info isKindOfClass:[NSNumber class]],@"This connection was not set up with a token.");
	return [(NSNumber *)info unsignedIntValue];
}

- (NSString *)type { 
	NSAssert(type != nil,@"Please setup the connection.");
	return type; 
}

#pragma mark actions setup

- (void)setupAsConnectionToName:(NSString *)name peerConnection:(BOOL)peerconn {
	NSAssert(type==nil && info==nil,@"%@ has already been identified.");
//NSLog(@"%@:%d identified as connection to %@ (%d).",[self addressHost],[self portHost],name,peerconn);
	[self willChangeValueForKey:@"type"];
	[self willChangeValueForKey:@"name"];
	info = [name retain];
	type = peerconn?TypeConnectionPeer:TypeConnectionTransfer;
	type = [type retain];
	[self didChangeValueForKey:@"name"];
	[self didChangeValueForKey:@"type"];
}

- (void)setupAsPierceFirewallWithToken:(unsigned)token {
	NSAssert(type==nil && info==nil,@"%@ has already been identified.");
//NSLog(@"%@:%d identified as a requested connection for %d.",[self addressHost],[self portHost],token);
	[self willChangeValueForKey:@"token"];
	[self willChangeValueForKey:@"type"];
	info = [[NSNumber alloc] initWithUnsignedInt:token];
	[type release];
	type = [TypeConnectionPierceFirewall retain];
	[self didChangeValueForKey:@"token"];
	[self didChangeValueForKey:@"type"];
}

@end

#pragma mark -


@interface TransferConnection (Private)
//- (void)identifyTransfer;
- (void)identifyOffset;
- (void)sendData;
- (void)startTransferring;
@end

@implementation TransferConnection

unsigned CountOfAllTransferConnections=0;
- (id)initWithUnidentifiedConnection:(UnidentifiedConnection *)uc peer:(Peer *)peer {
	return [self initWithUnidentifiedConnection:uc peer:peer transfer:nil];
}

- (id)initWithUnidentifiedConnection:(UnidentifiedConnection *)uc peer:(Peer *)peer transfer:(Transfer *)transfer {
	NSAssert(uc && ([TypeConnectionTransfer isEqual:[uc type]] || [[uc type] isEqual:TypeConnectionPierceFirewall]),
				@"UnidentifiedConnection must be set and its type must be TypeConnectionTransfer or TypeConnectionPierceFirewall.");
	if(self = [super initWithUnidentifiedConnection:uc peer:peer]) {
		_transfer = [transfer retain];
		automaticallyReadMessages = NO;
		_fileHandle = nil;
	//NSLog(@"INIT (un): %@. %d transfer connections.",transfer==nil?[peer name]:[[_transfer share] filenameShared],++CountOfAllTransferConnections);
	}
	return self;
}

- (id)initWithPeer:(Peer *)peer {
	if(self = [super initWithPeer:peer]) {
		_transfer = nil;
		automaticallyReadMessages = NO;
		_fileHandle = nil;
	//NSLog(@"INIT: %@. %d transfer connections.",[peer name],++CountOfAllTransferConnections);
	}
	return self;
}

- (id)initWithTransfer:(Transfer *)transfer {
	if(self = [self initWithPeer:[transfer peer]]) {
		_transfer = [transfer retain];
	}
	return self;
}

- (void)dealloc {
////NSLog(@"DEALLOC: %@. %d transfer connections.",[[_transfer share] filenameShared],--CountOfAllTransferConnections);
	[_fileHandle release];
	[_transfer release];
	_transfer = nil;
	[super dealloc];
////NSLog(@"DEALLOC: complete.");
}

- (BOOL)isEqual:(id)other {
	return self==other || [other isKindOfClass:[self class]] && [_transfer isEqual:[other transfer]];
}

- (unsigned)hash {
	return [_transfer hash]+1;
}

- (NSComparisonResult)compare:(id)other {
	return [other isKindOfClass:[self class]]?[_transfer compare:[other transfer]]:NSOrderedAscending;
}

#pragma mark KVO
- (NSString *)connectionType { return TypeConnectionTransfer; }
- (NSData *)dataTransferred { return _dataTransferred; }
- (unsigned)token { return _transfer==nil?_tokenUnidentified:[_transfer token]; }
- (Transfer *)transfer { return _transfer; }

#pragma mark KVC

- (void)setTransfer:(Transfer *)transfer {
	NSAssert(_transfer == nil,@"Transfer can only be set once.");
	NSAssert([self connected],@"TransferConnection must be connected.");
	
//NSLog(@"Set transfer for %d",transfer);
	
	_transfer = [transfer retain];
	[self identifyOffset];
}

#pragma mark actions
- (void)disconnect {
//NSLog(@"Disconnecting from %@ with %d",[self peer],self);
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(oops) object:nil];
	_stateConnection = MessageNone;
	[_transfer closeBuffer];
	[super disconnect];
}

- (void)sendOffset {
	if([_transfer openBuffer] == NO) {
		[self disconnect];
		[NSException raise:@"BufferException" format:@"Could not open the transfer buffer."];
	}
	unsigned long long offset = [_transfer countBytesTransferred];
//NSLog(@"TRANSFER: Sending an offset of %d for [%d]%@ (%d,%d).", offset, _peer,_peer,_transfer,self);
	offset = NSSwapHostLongLongToLittle(offset);
	NSData *d = [NSData dataWithBytes:&offset length:sizeof(offset)];
	[_socket writeData:d];
}

- (void)identifyOffset {
	//identify offset will
	//write the offset (long long) to the socket (if it is a download)
	//read the offset (long long) from the socket (if it is an upload)
	
//NSLog(@"TRANSFER: Identifying the offset for %@ (%d,%d).",_peer,_transfer,self);
	
	switch([_transfer type]) {
		case TransferTypeDownload: {
			//write the offset.  read the file.
			[self performSelector:@selector(sendOffset) withObject:nil afterDelay:0.0];
			[self startTransferring];
			break;
		}
		case TransferTypeUpload: {
			//read the offset (an unsigned long long). write the file.
			_stateConnection = TransferReadOffset;
			[self updateStatus];
			break;
		}
		default:
			[self disconnect];
			[NSException raise:@"UnknownTypeException" format:@"Cannot identify offset of transfer type %d.",[_transfer type]];
	}
}

- (void)sendToken {
	//write the transfer token (an unsigned int).
	NSMutableData *d = [NSMutableData dataWithCapacity:sizeof(unsigned)+sizeof(unsigned long long)];
	unsigned token = [_transfer token];
//NSLog(@"TRANSFER: Sending token of %d for %@ (%d,%d).",token, _peer,_transfer,self);
	token = NSSwapHostIntToLittle(token);
	[d appendBytes:&token length:sizeof(token)];
	[_socket writeData:d];
}
	

- (void)identifyTransfer {
//NSLog(@"TRANSFER: Identifying transfer for %@ (%d,%d).",_peer,_transfer,self);
	if(_transfer != nil) {	
		
		//	we requested the transfer so we know which one it is.  So tell them that.
		
		_stateConnection = TransferWriteToken;
		
		[self performSelector:@selector(sendToken) withObject:nil afterDelay:0.0];
		
		[self identifyOffset];
	}
	else {
		//read the transfer token (an unsigned int)

		//we didn't request this transfer BUT we did accept the request.
		//figure out which one it is exactly he wants to send.
	//NSLog(@"TRANSFER: Beginning to read the token.");
		_stateConnection = TransferReadToken;
		[self updateStatus];
	}
}

- (void)sendData {
	NSAssert([_transfer type]==TransferTypeUpload,@"Transfer must be an upload to send data.");
	NSData *d = [(TransferUpload *)_transfer readDataOfLength:2048];
	if([d length]) {
		[self willChangeValueForKey:@"dataTransferred"];
		[_socket writeData:d];
		[_dataTransferred release];
		_dataTransferred = [d retain];
	}
	else {
		[self performSelector:@selector(disconnect) withObject:nil afterDelay:5.0];
	}
}	


- (void)sendMessagesQueued {
//NSLog(@"TRANSFER: sending queued messages for %@ (%d,%d).",_peer,_transfer,self);
	[super sendMessagesQueued];
	[self identifyTransfer];
}


- (void)startTransferring {
//NSLog(@"TRANSFER: starting transfer for %@ (%d,%d).",_peer,_transfer,self);
	NSAssert(_transfer, @"Must have a transfer to startTransferring.");
	switch([_transfer type]) {
		case TransferTypeDownload: {
			_stateConnection = TransferTransferDownload;
			[self updateStatus];
			break;
		}
		case TransferTypeUpload: {
			_stateConnection = TransferTransferUpload;
			[self performSelector:@selector(sendData) withObject:nil afterDelay:0.0];
		}
	}
}

- (void)oops {
//NSLog(@"TRANSFER: OOPS!");
	[self disconnect];
}
			

- (void)updateStatus {
	unsigned available = [_socket incomingBufferLength];
	switch(_stateConnection) {
		case TransferReadToken: {
			if(available >= 4) {
				//update the token key.
				//don't do anything because if we are reading the token, we don't
				//actually know what this transfer is for yet...
				NSData *data = [_socket readData:4];
				unsigned t;
				[data getBytes:&t];
				[self willChangeValueForKey:@"token"];
				_tokenUnidentified = NSSwapLittleIntToHost(t);
			//NSLog(@"TRANSFER: Identified token as %d for %@ (%d,%d).", _tokenUnidentified, _peer,_transfer,self);
				[self didChangeValueForKey:@"token"];
				//now the controller must find the transfer based on the token.
				//the controller will then send an identifyOffset message.
			}
			else
				return;
			break;
		}
		case TransferReadOffset: {
			if(available >= sizeof(unsigned long long)) {
				NSAssert(_transfer && [_transfer type] == TransferTypeUpload,@"Transfer must be set and be an upload.");
				
				NSData *data = [_socket readData:sizeof(unsigned long long)];
				//update the offset key and read data!
				NSAssert1([data length] == sizeof(unsigned long long), @"The offset we read was the wrong size: %d.",[data length]);
				if([_transfer openBuffer]==NO) {
					[self disconnect];
					[NSException raise:@"BufferException" format:@"Could not open the upload buffer."];
				}
				unsigned long long l;
				[data getBytes:&l];
				l = NSSwapLittleLongLongToHost(l);
			//NSLog(@"TRANSFER: Requested to skip %qu bytes for %@ (%d,%d).", l, _peer,_transfer,self);
				[(TransferUpload *)_transfer seekToOffset:l];
				
				[self startTransferring];
			}
			else 
				return;
			break;
		}
		case TransferTransferDownload: {
			if(available >= 1024 || [_transfer countBytesRemaining] <= available ) {
				[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil];
				
				NSData *data = [_socket readData];

				NSAssert([data length] > 0,@"Should not have read zero bytes.");
				
				[(TransferDownload *)_transfer writeData:data];
				
				[self willChangeValueForKey:@"dataTransferred"];
				[_dataTransferred release];
				_dataTransferred = [data retain];
				[self didChangeValueForKey:@"dataTransferred"];
				
				if([_transfer countBytesRemaining] == 0) {					
				//NSLog(@"TRANSFER: Scheduling shut down.");
					[self performSelector:@selector(oops) withObject:nil afterDelay:3.0];
				}
			}
			return;
			break;	
		}
	}
	[super updateStatus];
}
		
#pragma mark Socket delegate methods


- (void)netsocketDataSent:(NetSocket *)sock {
	NSAssert2(sock==_socket,@"We are not the delegate for %@:%d.",[sock remoteHost],[sock remotePort]);
	switch(_stateConnection) {
		case TransferTransferUpload: {
			[self didChangeValueForKey:@"dataTransferred"];
			[self sendData];
			break;
		}
		default:
			[super netsocketDataSent:sock];
	}
}

- (void)netsocketDisconnected:(NetSocket *)sock {
	NSAssert2(sock==_socket,@"We are not the delegate for %@:%d.",[sock remoteHost],[sock remotePort]);
//NSLog(@"TRANSFER: Disconnected %d %d %@.",self,_transfer,[_transfer filenameBuffer]);
	[_transfer closeBuffer];
	[super netsocketDisconnected:sock];
//NSLog(@"TRANSFER: Disconnect finished.");
}


@end